from abc import ABC, abstractmethod
from selenium.webdriver.edge.webdriver import WebDriver
from selenium.webdriver.edge.options import Options
from selenium.webdriver.edge.service import Service
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

import datetime
import time
import requests
import typing

# 子模块类
from .module_process import BrowserController
from .module_finderoperation import FinderElementBase
from .module_mouseoperation import MouseOperationBase
from .module_keyboardoperation import KeyboardOperationBase
from .module_log import Logger
from .PythonAssist import *









class ECommerceAutomation(ABC):
    def __init__(self):
        super().__init__()

        # self._driver: WebDriver = None
        # self._current_element: WebElement = None
        # self._browser: BrowserController = None
        # self._finder : FinderElement = None
        # self._mouseoperation: MouseOperation = None
        # self._baseoperation: BaseOperation = None
        # self._keyboardoperation: KeyboardOperation = None
        self._driver = None
        self._current_element = None
        self._browser = None
        self._finder  = None
        self._mouseoperation = None
        self._baseoperation = None
        self._keyboardoperation = None
        self._log = None
        
    def InitDriver(self) -> typing.Self:
        """初始化浏览器驱动"""
        try:
            options = Options()
            service = Service()
            options.add_experimental_option(
                "debuggerAddress", "127.0.0.1:9652")
            self._driver = WebDriver(options=options)
        except Exception as e:
            print(f"初始化浏览器驱动失败：{e}")
            exit(0)
        return self


    @property
    def driver(self) -> WebDriver:
        return self._driver

    @property
    def current_element(self) -> WebElement:
        return self._current_element


    @current_element.setter
    def current_element(self, value: WebElement | None):
        # 1. 类型校验：只允许赋值 WebElement 实例或 None（None 用于清空当前元素）
        if not isinstance(value, (WebElement, type(None))):
            raise TypeError(
                f"current_element 仅支持 WebElement 或 None 类型，当前传入类型：{type(value).__name__}")
        # 2. 校验通过，赋值给私有变量 _current_element
        self._current_element = value
    

    @property
    def browser(self) -> BrowserController:
        """基础操作模块"""
        if self._browser is None:
            self._browser = BrowserController
        return self._browser


    @property
    def base(self):
        """基础操作模块"""
        if self._baseoperation is None:
            self._baseoperation = BaseOperation(parent=self)
        return self._baseoperation


    @property
    def finder(self):
        """元素查找模块"""
        if self._finder is None:
            self._finder = FinderElement(parent=self)
        return self._finder


    @property
    def keyboard(self):
        """键盘操作模块"""
        if self._keyboardoperation is None:
            self._keyboardoperation = KeyboardOperation(parent=self)
        return self._keyboardoperation


    @property
    def mouse(self):
        """鼠标操作模块"""
        if self._mouseoperation is None:
            self._mouseoperation = MouseOperation(parent=self)
        return self._mouseoperation


    @property
    def log(self):
        """日志模块"""
        if self._log is None:
            self._log = Logger
        return self._log









class BaseOperation():
    """基础操作"""

    def __init__(self, parent):
        super().__init__()
        self._parent = parent

    def wait(self, seconds: int = 1) -> typing.Self:
        """等待"""
        time.sleep(seconds)
        return self

    def Goto_CurrentActivateUrl(self) -> typing.Self:
        """跳转到当前激活的页面"""
        debug_json_data = requests.get(
            url="http://127.0.0.1:9652/json", timeout=5).json()

        # 判断获取回来的数据是否正确
        if not isinstance(debug_json_data, list):
            return None

        # 过来掉不需要的 page 页面
        filter_json_data = list(
                                    filter(lambda item: (
                                        item['type'] == "page" and
                                        not item['title'].lower().startswith("devtools") and
                                        not item['title'].lower().startswith("offscreen") and
                                        not item['title'].lower().startswith("新标签页") and
                                        not item['url'].lower().startswith("edge://newtab/") and
                                        not item['url'].lower().startswith("chrome-extension") and
                                        not item['url'].lower().startswith("chrome-devtools")
                                    ), debug_json_data)
                                )
        current_activate_handle = filter_json_data[0]['id']
        self.parent.driver.switch_to.window(current_activate_handle)
        return self

    def Close_Browser_Dialog(self) -> typing.Self:
        """关闭浏览器弹窗"""
        Close_Browser_Dialog()
        return self

    @property
    def parent(self):
        return self._parent










class FinderElement(FinderElementBase):
    def __init__(self, parent):
        super().__init__(driver=parent.driver)
        self._parent = parent
        self.driver: WebDriver = self._parent.driver

    def chain_find_element(self, xpath) -> typing.Self:
        self.parent.current_element = self.find_element(xpath=xpath)
        return self

    def chain_find_element_safe(self, xpath, timeout=10) -> typing.Self:
       self.parent.current_element = self.find_element_safe(xpath=xpath, timeout=timeout)
       if self.parent.current_element is None:self.parent.log.error(f"查找元素失败，xpath：{xpath}")
       return self

    def chain_find_element_with_condition(self, xpath, condition_xpath: str = None, timeout=10) -> typing.Self:
        self.parent.current_element = self.find_element_with_condition(xpath=xpath, condition_xpath=condition_xpath, timeout=timeout)
        if self.parent.current_element is None:self.parent.log.error(f"查找元素失败，xpath：{xpath}")
        return self

    def chain_find_element_smart(self, source_element: typing.Union[WebElement, str, None], target_xpath: str, max_parent_levels: int = 5) -> typing.Self:
        self.parent.current_element = self.find_element_smart(source_element=source_element, target_xpath=target_xpath, max_parent_levels=max_parent_levels)
        return self

    def chain_by_text_find_element(self, text: str, text_type: typing.Literal["equal", "contains"], target_xpath: str, max_parent_levels: int = 5) -> typing.Self:
        self.parent.current_element = self.by_text_find_element(text=text, text_type=text_type, target_xpath=target_xpath, max_parent_levels=max_parent_levels)
        return self

    def chain_by_text_find_input_element(self, text: str, text_type: typing.Literal["equal", "contains"], target_xpath: str = "input", max_parent_levels: int = 5) -> typing.Self:
        self.parent.current_element = self.by_text_find_input_element(text=text, text_type=text_type, target_xpath=target_xpath, max_parent_levels=max_parent_levels)
        return self

    def chain_by_text_find_inputUpload_Element(self, text: str, text_type: typing.Literal["equal", "contains"], target_xpath: str = "input[@type='file']", max_parent_levels: int = 5) -> typing.Self:
        self.parent.current_element = self.by_text_find_inputUpload_Element(text=text, text_type=text_type, target_xpath=target_xpath, max_parent_levels=max_parent_levels)
        return self

    def chain_by_text_find_inputUploadImage_Element(self, text: str, text_type: typing.Literal["equal", "contains"], target_xpath: str = "input[@type='file'][contains(@accept,'image')]", max_parent_levels: int = 5) -> typing.Self:
        self.parent.current_element = self.by_text_find_inputUploadImage_Element(text=text, text_type=text_type, target_xpath=target_xpath, max_parent_levels=max_parent_levels)
        return self

    def chain_by_text_find_inputUploadVideo_Element(self, text: str, text_type: typing.Literal["equal", "contains"], target_xpath: str = "input[@type='file'][contains(@accept,'video')]", max_parent_levels: int = 5) -> typing.Self:
        self.parent.current_element = self.by_text_find_inputUploadVideo_Element(text=text, text_type=text_type, target_xpath=target_xpath, max_parent_levels=max_parent_levels)
        return self

    def chain_verify_element(self,xpath:str,timeout=5) -> typing.Self | None:
        if self.verify_element(xpath=xpath,timeout=timeout): return self
    
    def chain_wait_element(self,xpath:str,timeout=5) -> typing.Self | None:
        if self.wait_element(xpath=xpath,timeout=timeout) : return self

    # 返回父类对象
    @property
    def parent(self):
        return self._parent










class MouseOperation(MouseOperationBase):
    def __init__(self, parent):
        super().__init__()
        self._parent = parent

    # def click_element(self):
    #     try:
    #         self.parent.current_element.click()
    #     except:
    #         self.driver.execute_script("arguments[0].click();", self.parent.current_element)

    # def scroll_click_element(self):
    #     try:
    #         self.driver.execute_script("arguments[0].scrollIntoView({block: 'center', inline: 'center'});", self.parent.current_element)
    #         self.parent.current_element.click()
    #     except:
    #         self.driver.execute_script("arguments[0].click();", self.parent.current_element)

    # def chain_actions_click(self) -> typing.Self:
    #     self.actions_click()
    #     return self

    # def chain_safe_click(self) -> typing.Self:
    #     self.safe_click()
    #     return self

    # def chain_scroll(self) -> typing.Self:
    #     self.script_scroll()
    #     return self

    def chain_click(self) -> typing.Self:
        self.click()
        return self
    
    def chain_move_to_element(self) -> typing.Self:
        self.move_to_element()
        return self
    
    def chain_move_to_element_with_offset(self, x: int, y: int) -> typing.Self:
        self.chain_move_to_element_with_offset(x=x, y=y)
        return self

    def chain_move_by_offset(self, x: int, y: int) -> typing.Self:
        self.move_by_offset(x=x,y=y)
        return self

    def chain_scroll_click_element(self) -> typing.Self:
        self.scroll_click_element()
        return self

    def chain_script_hover(self, duration: float = 0.5, offset_x: int = 0, offset_y: int = 0) -> typing.Self:
        self.script_hover(duration=duration,offset_x=offset_x,offset_y=offset_y)
        return self

    def chain_script_scroll(self) -> typing.Self:
        self.script_scroll()
        return self

    def chain_script_click(self) -> typing.Self:
        self.script_click()
        return self

    def chain_safe_click(self) -> typing.Self:
        self.safe_click()
        return self

    def chain_condition_click(self,condition_xpath:str,timeout=5,expect_exists=True) -> typing.Self:
        self.condition_click(condition_xpath=condition_xpath,timeout=timeout,expect_exists=expect_exists)
        return self

    @property
    def parent(self):
        return self._parent
    
    @property
    def element(self) -> WebElement:
        return self.parent.current_element

    @property
    def driver(self) -> WebDriver:
        return self.parent.driver





    

class KeyboardOperation(KeyboardOperationBase):
    def __init__(self, parent):
        super().__init__()
        self._parent = parent

    def chain_only_clear(self) -> typing.Self:
        self.only_clear()
        return self

    def chain_only_input(self, InputValue) -> typing.Self:
        self.only_input(InputValue=InputValue)
        return self
    
    def chain_fast_input(self, InputValue) -> typing.Self:
        self.fast_input(InputValue=InputValue)
        return self
    
    def chain_check_clear(self, attribute: str = "value",timeout = 5) -> typing.Self:
        self.check_clear(attribute=attribute,timeout=timeout)
        return self

    def chain_check_input(self, InputValue, attribute: str = "value") -> typing.Self:
        self.check_input(InputValue=InputValue, attribute=attribute)
        return self
    
    def chain_fast_input(self, InputValue) -> typing.Self:
        self.fast_input(InputValue=InputValue)
        return self
    
    def chain_safe_clear(self, attribute = None, timeout=5) -> typing.Self:
        self.safe_clear(attribute=attribute,timeout=timeout)
        return self

    def chain_safe_input(self,InputValue:str,attribute:str=None,timeout = 5) -> typing.Self:
        self.safe_input(InputValue=InputValue,attribute=attribute,timeout=timeout)
        return self

    def chain_send_keys(self,InputValue:str) -> typing.Self:
        self.only_input(InputValue=InputValue)
        return self

    def chain_condition_clean(self,condition_xpath:str,timeout=5) -> typing.Self:
        self.condition_clean(condition_xpath=condition_xpath,timeout=timeout)
        return self
    
    def chain_condition_input(self,InputValue:str,condition_xpath:str,timeout=5) -> typing.Self:
        self.condition_input(InputValue=InputValue,condition_xpath=condition_xpath,timeout=timeout)
        return self

    # def chain_clear(self, attribute: str = "value") -> typing.Self:
    #     self.check_clear(attribute=attribute)
    #     return self

    # def chain_input(self, InputValue, attribute: str = "value", timeout=5) -> typing.Self:
    #     self.check_input(InputValue=InputValue, attribute=attribute, timeout=timeout)
    #     return self

    # def chain_safe_input(self,text:str,attribute:str="value") -> typing.Self:
    #     self.safe_input(text=text,attribute=attribute)
    #     return self

    # def chain_send_keys(self,text:str) -> typing.Self:
    #     self.element.send_keys(text)
    #     return self

    @property
    def parent(self):
        return self._parent
    
    @property
    def element(self) -> WebElement:
        return self.parent.current_element


    @property
    def driver(self) -> WebDriver:
        return self.parent.driver

